package com.smsc.headend.task.engine.executor.impl.generalparam;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.enums.PaymentSwithPreToPostOptGroupEnum;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.MeterConfigDMO;
import com.smsc.headend.module.asset.dto.VendingParameterMessageDto;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.data.dto.DLMSSubDataItemDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.data.enums.OperationGroup;
import com.smsc.headend.module.data.enums.OperationName;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.report.entity.LogModeSwitchHisSub;
import com.smsc.headend.module.scheme.entity.ConfigScheduleTask;
import com.smsc.headend.module.task.consts.TaskKafkaTopic;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.dto.TaskResult;
import com.smsc.headend.module.task.enums.DeviceActionType;
import com.smsc.headend.module.task.enums.SubTaskStatus;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.deviceconfig.ReadMeterPaymentForSwitchToPostHandler;
import com.smsc.headend.task.engine.handler.impl.deviceconfig.ReadMeterPaymentHandler;
import com.smsc.headend.task.engine.handler.impl.generalparam.ReadGeneralParamHandler;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * @author wxh
 * @date 2021/5/11 9:08
 */
public class GetGeneralParamExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;

    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    AssetService assetService;
    @Autowired
    DataItemFacade dataItemFacade;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    KafkaTemplate kafkaTemplate;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());
        Meter meter = getMeter(task, assetService);
        MeterConfigDMO meterConfigDMO = task.getMeterId() == null ? null : assetService.getMeterConfigById(task.getMeterId());
        ComEntity terminal = assetService.getComById(task.getComId());
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());

        List<DataItemDTO> dataItemCosemList = dataItemFacade.getDataItemsByOperation(meter, terminal, dictionary, getParameterMap(task, atomicTask), comConfigDMO.getModelId());
        if (CollectionUtils.isEmpty(dataItemCosemList)) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATAITEM_NOT_EXISTS.toString());
        }
        DataItemDTO dataItemDto = dataItemCosemList.get(0);
        if (((DLMSSubDataItemDTO) dataItemDto).getClassId() == 300) {
            // classId为300 自定义为 GPRS AT指令
            if (!StrUtil.equals("P2P", meter.getChannelType())) {
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.NOT_P2P_METER.toString());
            }
            Map<String, Object> map = JSONUtil.toBean(atomicTask.getAtomicTaskParam(), Map.class);
            if (map.get("value") == null || !Convert.toStr(map.get("value")).startsWith("AT+")) {
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.INVALID_AT_CMD.toString());
            }
            String atCmd = Convert.toStr(map.get("value")).trim();
            byte[][] packets = new byte[][]{atCmd.getBytes(StandardCharsets.UTF_8)};
            log.info(ByteUtils.byteToHexString(packets[0]));
            connectorService.sendAtCmdPacket(task, atomicTask, dataItemCosemList.get(0).getRoleId(), task.getIsODR(), false, packets);
        } else {
            DeviceSAPandKeys deviceKeys = meter != null ? assetManagementRemoteService.getMeterCommunicationKey(meter.getMeterId(), dataItemDto.getRoleId()) :
                    assetManagementRemoteService.getTerminalCommunicationKey(terminal.getComId(), dataItemDto.getRoleId());
            byte[] packets = packetService.buildReadOneAttributePacket(dataItemCosemList.get(0), meterConfigDMO, deviceKeys);
            if (packets == null) {
                log.error("build packet failed: dataItem={}, config={}, deviceKeys={}", dataItemCosemList.get(0), meterConfigDMO, deviceKeys);
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.BUILD_REQUEST_FAIL.toString());
            }
            log.debug(ByteUtils.byteToHexString(packets));
            connectorService.sendPacket(task, atomicTask, dataItemCosemList.get(0).getRoleId(), task.getIsODR(), packets);
        }
        if (task.getTaskType().equals(TaskType.SwitchMeterPayment.toString()) && isSubTaskGroupFirstAtomicTask(task, atomicTask)) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            assetManagementRemoteService.updateStepStatus(taskId, task.getMeterId(), LogModeSwitchHisSub.Step.ReadData, SubTaskStatus.Processing.getCode(), SystemClock.now() / 1000, null, null);
        }

        if (task.getTaskType().equals(TaskType.SwitchMeterPaymentPreToPost.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            DeviceOperation operationGroup = assetService.getDictionary(atomicTask.getGroupId(), DictionaryType.OperationGroup.getType());
            OperationGroup operationGp = OperationGroup.forId(operationGroup.getOptId().intValue());
            if (operationGp.getId().equals(OperationGroup.ReadPaymentMode.getId())) {
                assetManagementRemoteService.updateStepStatusToPost(taskId, task.getTaskNo(), task.getMeterId(), PaymentSwithPreToPostOptGroupEnum.READ_PAYMENT_MODE.getOperationGroup().longValue(), SubTaskStatus.Processing.getCode(), SystemClock.now() / 1000, null, null, false);
            }
        }

        if (atomicTask.getGroupId() == OperationGroup.SwitchMeterPayment.getId().intValue()) {
            return createAtomicState(ReadMeterPaymentHandler.class);
        }
        if (atomicTask.getGroupId() == OperationGroup.ReadPaymentMode.getId().intValue() || atomicTask.getGroupId() == OperationGroup.SwitchPaymentFromPreToPost.getId().intValue()) {
            return createAtomicState(ReadMeterPaymentForSwitchToPostHandler.class);
        }
        if (StrUtil.equalsIgnoreCase(task.getTaskType(),TaskType.GetRelayControlMode.name())) {
            assetManagementRemoteService.updateLogRelayControl(task.getTaskNo(), meter.getMeterId(), ConfigScheduleTask.ActionType.GET_DEVICE_CONFIG.getType(), SubTaskStatus.Processing.getCode(),null, null);
        }
        return createAtomicState(ReadGeneralParamHandler.class);
    }

    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        if (task.getTaskType().equals(TaskType.SwitchMeterPayment.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            Long now = SystemClock.now() / 1000;
            assetManagementRemoteService.updateStepStatus(taskId, task.getMeterId(), atomicTask.getGroupId() == OperationGroup.SwitchMeterPayment.getId().intValue() ? LogModeSwitchHisSub.Step.SwitchMeterPayment : LogModeSwitchHisSub.Step.ReadData,
                    SubTaskStatus.Failed.getCode(), isSubTaskGroupFirstAtomicTask(task, atomicTask) ? now : null, now, String.format("execute %s fail, [%s]", OperationName.forId(atomicTask.getOperationId()).getName(), message));
        } else if (task.getTaskType().equals(TaskType.WsMdmMeterReading.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            String refVale = MapUtil.getStr(parameterMap, "refValue");
            Meter meter = getMeter(task, assetService);
            String errorMessage = "";
            if (!isSubTaskGroupLastAtomicTask(task, atomicTask)) {
                removeSetAtomicTaskIfExists(task, atomicTask);
            }
            if (StrUtil.isNotEmpty(message)) {
                errorMessage = String.format("The meter[%s] item [%s] error: [%s]", meter.getCustomerSn(), refVale, message);
            } else {
                errorMessage = String.format("The  meter[%s] item [%s] failed", meter.getCustomerSn(), refVale);
            }
            // send to adapter
            Map<String, Object> taskMap = JSONUtil.toBean(task.getParamJson(), Map.class);
            String vendingXmlStr = (String) taskMap.get("vendingXmlRequest");
            sendVendingResultToAdapter(VendingParameterMessageDto.ActionType.GetMeterReadings.name(), vendingXmlStr, null, errorMessage, "0.2");
        } else if (task.getTaskType().equals(TaskType.SwitchMeterPaymentPreToPost.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            Long step = atomicTask.getGroupId();
            assetManagementRemoteService.updateStepStatusToPost(taskId, task.getTaskNo(), task.getMeterId(), step, SubTaskStatus.Failed.getCode(), null, SystemClock.now() / 1000,
                    String.format("handle %s fail, [%s]", OperationName.forId(atomicTask.getOperationId()).getName(), message), false);
        } else if (StrUtil.equalsIgnoreCase(task.getTaskType(),TaskType.GetRelayControlMode.name())) {
            assetManagementRemoteService.updateLogRelayControl(task.getTaskNo(), task.getMeterId(), ConfigScheduleTask.ActionType.GET_DEVICE_CONFIG.getType(), SubTaskStatus.Failed.getCode(),null, message);
        } else {
            log.info("error {}, ", message);
            Map<String, Object> param = getParameterMap(task, atomicTask);
            Long subDataItemId = Long.valueOf(String.valueOf(param.get("subDataItemId")));
            if (task.getIsODR()) {
                taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), Convert.toStr(subDataItemId), message, 600L, false);
            }
        }
        return true;
    }

    private void removeSetAtomicTaskIfExists(Task task, AtomicTask atomicTask) {
        if (StrUtil.equalsIgnoreCase(TaskType.WsMdmMeterReading.toString(), task.getTaskType())) {
            //失败的话 结束后续所有任务
            AtomicTask disconnectTask = taskDAO.atomicTaskQueuePopReverse(task.getTaskNo());
            String termQueueId = taskDAO.getTerminalTaskListKey(task);
            taskDAO.terminalTaskQueuePop(termQueueId);
            taskDAO.atomicTaskQueueDelete(task.getTaskNo());
            taskDAO.clearTaskProcessingAtomic(task.getTaskNo());
            log.info("remove vending meterReading atomicTask {} ", disconnectTask.getAtomicTaskNo());
        }
    }

    private void sendVendingResultToAdapter(String vendingType, String requestXmlMsg, TaskResult taskResult, String errorMsg, String replyCode) {
        if (StrUtil.isNotEmpty(errorMsg)) {
            log.info(errorMsg);
        }
        JSONObject obj = JSONUtil.createObj();
        obj.put("VendingType", vendingType);
        obj.put("vendingXmlRequest", requestXmlMsg);
        if (StrUtil.isNotEmpty(errorMsg)) {
            obj.put("errorMessage", errorMsg);
        }
        obj.put("replyCode", replyCode);
        if (taskResult != null) {
            obj.put("taskResult", taskResult);
        }

        kafkaTemplate.send(TaskKafkaTopic.TASK_VENDING_RESULT_TO_ADAPTER, JSONUtil.toJsonStr(obj).getBytes(StandardCharsets.UTF_8));
    }
}
